/* -LICENSE-START-
** Copyright (c) 2009 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation covered by
** this license (the "Software") to use, reproduce, display, distribute,
** execute, and transmit the Software, and to prepare derivative works of the
** Software, and to permit third-parties to whom the Software is furnished to
** do so, all subject to the following:
** 
** The copyright notices in the Software and this entire statement, including
** the above license grant, this restriction and the following disclaimer,
** must be included in all copies of the Software, in whole or in part, and
** all derivative works of the Software, unless such copies or derivative
** works are solely in the form of machine-executable object code generated by
** a source language processor.
** 
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
** -LICENSE-END-
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <libgen.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>

#include "TestPattern.h"

pthread_mutex_t			sleepMutex;
pthread_cond_t			sleepCond;

const unsigned long		kAudioWaterlevel = 48000;

void	sigfunc (int signum)
{
	pthread_cond_signal(&sleepCond);
}

int main(int argc, char *argv[])
{
	TestPattern generator;
	
	signal(SIGINT, sigfunc);
	pthread_mutex_init(&sleepMutex, NULL);
	pthread_cond_init(&sleepCond, NULL);
	
	if (!generator.Init())
		return 1;

	return 0;
}

TestPattern::TestPattern()
{
	m_audioChannelCount = 2;
	m_audioSampleRate = bmdAudioSampleRate48kHz;
	m_audioSampleDepth = 16;
	m_running = false;
	m_outputSignal = kOutputSignalDrop;
}

bool	TestPattern::Init()
{
	// Initialize the DeckLink API
	IDeckLinkIterator*			deckLinkIterator = CreateDeckLinkIteratorInstance();
	HRESULT						result;
	
	if (!deckLinkIterator)
	{
		fprintf(stderr, "This application requires the DeckLink drivers installed.\n");
		goto bail;
	}
	
	// Connect to the first DeckLink instance
	result = deckLinkIterator->Next(&m_deckLink);
	if (result != S_OK)
	{
		fprintf(stderr, "No DeckLink PCI cards found\n");
		goto bail;
	}
	
	// Obtain the audio/video output interface (IDeckLinkOutput)
	if (m_deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&m_deckLinkOutput) != S_OK)
		goto bail;
	
	// Provide this class as a delegate to the audio and video output interfaces
	m_deckLinkOutput->SetScheduledFrameCompletionCallback(this);
	m_deckLinkOutput->SetAudioCallback(this);
	
	// Start.
	StartRunning();
	
	pthread_mutex_lock(&sleepMutex);
	pthread_cond_wait(&sleepCond, &sleepMutex);
	pthread_mutex_unlock(&sleepMutex);
	fprintf(stderr, "Bailling out\n");
	
bail:
	if (m_running == true)
	{
		StopRunning();
	}
	else
	{
		// Release any resources that were partially allocated
		if (m_deckLinkOutput != NULL)
		{
			m_deckLinkOutput->Release();
			m_deckLinkOutput = NULL;
		}
		//
		if (m_deckLink != NULL)
		{
			m_deckLink->Release();
			m_deckLink = NULL;
		}
	}
	
	if (deckLinkIterator != NULL)
		deckLinkIterator->Release();
	
	return true;
}
	
IDeckLinkDisplayMode*	TestPattern::GetDisplayModeByIndex(int selectedIndex)
{
	// Populate the display mode combo with a list of display modes supported by the installed DeckLink card
	IDeckLinkDisplayModeIterator*		displayModeIterator;
	IDeckLinkDisplayMode*				deckLinkDisplayMode;
	IDeckLinkDisplayMode*				selectedMode = NULL;
	int index = 0;

    if (m_deckLinkOutput->GetDisplayModeIterator(&displayModeIterator) != S_OK)
        goto bail;
    while (displayModeIterator->Next(&deckLinkDisplayMode) == S_OK)
    {
        const char       *modeName;

        if (deckLinkDisplayMode->GetName(&modeName) == S_OK)
        {
			if (index == selectedIndex)
			{
				printf("Selected mode: %s\n", modeName);
				selectedMode = deckLinkDisplayMode;
				goto bail;
			}
        }
		index++;
    }
bail:
    displayModeIterator->Release();
	return selectedMode; 
}

void	TestPattern::StartRunning ()
{
	IDeckLinkDisplayMode*	videoDisplayMode = NULL;
	unsigned long			audioSamplesPerFrame;
	
	// Get the display mode for 1080i 59.95
	videoDisplayMode = GetDisplayModeByIndex(6);

	if (!videoDisplayMode)
		return;

	m_frameWidth = videoDisplayMode->GetWidth();
	m_frameHeight = videoDisplayMode->GetHeight();
	videoDisplayMode->GetFrameRate(&m_frameDuration, &m_frameTimescale);
	
	// Calculate the number of frames per second, rounded up to the nearest integer.  For example, for NTSC (29.97 FPS), framesPerSecond == 30.
	m_framesPerSecond = (unsigned long)((m_frameTimescale + (m_frameDuration-1))  /  m_frameDuration);
	
	// Set the video output mode
	if (m_deckLinkOutput->EnableVideoOutput(videoDisplayMode->GetDisplayMode(), bmdVideoOutputFlagDefault) != S_OK)
	{
		fprintf(stderr, "Failed to enable video output\n");
		goto bail;
	}
	
	// Set the audio output mode
	if (m_deckLinkOutput->EnableAudioOutput(bmdAudioSampleRate48kHz, m_audioSampleDepth, m_audioChannelCount, bmdAudioOutputStreamContinuous) != S_OK)
	{
		fprintf(stderr, "Failed to enable audio output\n");
		goto bail;
	}
	
	// Generate one second of audio
	m_audioBufferSampleLength = (unsigned long)((m_framesPerSecond * m_audioSampleRate * m_frameDuration) / m_frameTimescale);
	m_audioBuffer = valloc(m_audioBufferSampleLength * m_audioChannelCount * (m_audioSampleDepth / 8));

	if (m_audioBuffer == NULL)
	{
		fprintf(stderr, "Failed to allocate audio buffer memory\n");	
		goto bail;
	}
	
	// Zero the buffer (interpreted as audio silence)
	memset(m_audioBuffer, 0x0, (m_audioBufferSampleLength * m_audioChannelCount * m_audioSampleDepth/8));
	audioSamplesPerFrame = (unsigned long)((m_audioSampleRate * m_frameDuration) / m_frameTimescale);
	
	if (m_outputSignal == kOutputSignalPip)
		FillSine(m_audioBuffer, audioSamplesPerFrame, m_audioChannelCount, m_audioSampleDepth);
	else
		FillSine((void*)((unsigned long)m_audioBuffer + (audioSamplesPerFrame * m_audioChannelCount * m_audioSampleDepth/8)), (m_audioBufferSampleLength - audioSamplesPerFrame), m_audioChannelCount, m_audioSampleDepth);
	
	// Generate a frame of black
	if (m_deckLinkOutput->CreateVideoFrame(m_frameWidth, m_frameHeight, m_frameWidth*2, bmdFormat8BitYUV, bmdFrameFlagDefault, &m_videoFrameBlack) != S_OK)
	{
		fprintf(stderr, "Failed to create video frame\n");	
		goto bail;
	}
	FillBlack(m_videoFrameBlack);
	
	// Generate a frame of colour bars
	if (m_deckLinkOutput->CreateVideoFrame(m_frameWidth, m_frameHeight, m_frameWidth*2, bmdFormat8BitYUV, bmdFrameFlagDefault, &m_videoFrameBars) != S_OK)
	{
		fprintf(stderr, "Failed to create video frame\n");
		goto bail;
	}
	FillColourBars(m_videoFrameBars);
	
	// Begin video preroll by scheduling a second of frames in hardware
	m_totalFramesScheduled = 0;
	for (unsigned i = 0; i < m_framesPerSecond; i++)
		ScheduleNextFrame(true);
	
	// Begin audio preroll.  This will begin calling our audio callback, which will start the DeckLink output stream.
	m_audioBufferOffset = 0;
	if (m_deckLinkOutput->BeginAudioPreroll() != S_OK)
	{
		fprintf(stderr, "Failed to begin audio preroll\n");
		goto bail;
	}
	
	m_running = true;
	
	return;
	
bail:
	// *** Error-handling code.  Cleanup any resources that were allocated. *** //
	StopRunning();
}


void	TestPattern::StopRunning ()
{
	// Stop the audio and video output streams immediately
	m_deckLinkOutput->StopScheduledPlayback(0, NULL, 0);
	//
	m_deckLinkOutput->DisableAudioOutput();
	m_deckLinkOutput->DisableVideoOutput();
	
	if (m_videoFrameBlack != NULL)
		m_videoFrameBlack->Release();
	m_videoFrameBlack = NULL;
	
	if (m_videoFrameBars != NULL)
		m_videoFrameBars->Release();
	m_videoFrameBars = NULL;
	
	if (m_audioBuffer != NULL)
		free(m_audioBuffer);
	m_audioBuffer = NULL;
	
	// Success; update the UI
	m_running = false;
}


void	TestPattern::ScheduleNextFrame (bool prerolling)
{
	if (prerolling == false)
	{
		// If not prerolling, make sure that playback is still active
		if (m_running == false)
			return;
	}
	if (m_outputSignal == kOutputSignalPip)
	{
		if ((m_totalFramesScheduled % m_framesPerSecond) == 0)
		{
			// On each second, schedule a frame of bars
			if (m_deckLinkOutput->ScheduleVideoFrame(m_videoFrameBars, (m_totalFramesScheduled * m_frameDuration), m_frameDuration, m_frameTimescale) != S_OK)
				return;
		}
		else
		{
			// Schedue frames of black
			if (m_deckLinkOutput->ScheduleVideoFrame(m_videoFrameBlack, (m_totalFramesScheduled * m_frameDuration), m_frameDuration, m_frameTimescale) != S_OK)
				return;
		}
	}
	else
	{
		if ((m_totalFramesScheduled % m_framesPerSecond) == 0)
		{
			// On each second, schedule a frame of black
			if (m_deckLinkOutput->ScheduleVideoFrame(m_videoFrameBlack, (m_totalFramesScheduled * m_frameDuration), m_frameDuration, m_frameTimescale) != S_OK)
				return;
		}
		else
		{
			// Schedue frames of color bars
			if (m_deckLinkOutput->ScheduleVideoFrame(m_videoFrameBars, (m_totalFramesScheduled * m_frameDuration), m_frameDuration, m_frameTimescale) != S_OK)
				return;
		}
	}
	
	m_totalFramesScheduled += 1;
}

void	TestPattern::WriteNextAudioSamples ()
{
	unsigned int		bufferedSamples;
	
	// Try to maintain the number of audio samples buffered in the API at a specified waterlevel
	if ((m_deckLinkOutput->GetBufferedAudioSampleFrameCount(&bufferedSamples) == S_OK) && (bufferedSamples < kAudioWaterlevel))
	{
		unsigned int		samplesToEndOfBuffer;
		unsigned int		samplesToWrite;
		unsigned int		samplesWritten;
		
		samplesToEndOfBuffer = (m_audioBufferSampleLength - m_audioBufferOffset);
		samplesToWrite = (kAudioWaterlevel - bufferedSamples);
		if (samplesToWrite > samplesToEndOfBuffer)
			samplesToWrite = samplesToEndOfBuffer;
		
		if (m_deckLinkOutput->ScheduleAudioSamples((void*)((unsigned long)m_audioBuffer + (m_audioBufferOffset * m_audioChannelCount * m_audioSampleDepth/8)), samplesToWrite, 0, 0, &samplesWritten) == S_OK)
		{
			m_audioBufferOffset = ((m_audioBufferOffset + samplesWritten) % m_audioBufferSampleLength);
		}
	}
}

/************************* DeckLink API Delegate Methods *****************************/

HRESULT		TestPattern::ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
{
	// When a video frame has been released by the API, schedule another video frame to be output
	ScheduleNextFrame(false);
	return S_OK;
}

HRESULT		TestPattern::ScheduledPlaybackHasStopped ()
{
	return S_OK;
}

HRESULT		TestPattern::RenderAudioSamples (bool preroll)
{
	// Provide further audio samples to the DeckLink API until our preferred buffer waterlevel is reached
	WriteNextAudioSamples();
	
	if (preroll)
	{
		// Start audio and video output
		m_deckLinkOutput->StartScheduledPlayback(0, 100, 1.0);
	}
	
	return S_OK;
}

/*****************************************/


void	FillSine (void* audioBuffer, unsigned long samplesToWrite, unsigned long channels, unsigned long sampleDepth)
{
	if (sampleDepth == 16)
	{
		short*		nextBuffer;
		
		nextBuffer = (short*)audioBuffer;
		for (unsigned i = 0; i < samplesToWrite; i++)
		{
			short		sample;
			
			sample = (short)(24576.0 * sin((i * 2.0 * M_PI) / 48.0));
			for (unsigned ch = 0; ch < channels; ch++)
				*(nextBuffer++) = sample;
		}
	}
	else if (sampleDepth == 32)
	{
		int*		nextBuffer;
		
		nextBuffer = (int*)audioBuffer;
		for (unsigned i = 0; i < samplesToWrite; i++)
		{
			int		sample;
			
			sample = (int)(1610612736.0 * sin((i * 2.0 * M_PI) / 48.0));
			for (unsigned ch = 0; ch < channels; ch++)
				*(nextBuffer++) = sample;
		}
	}
}

void	FillColourBars (IDeckLinkVideoFrame* theFrame)
{
	unsigned int*			nextWord;
	unsigned long	width;
	unsigned long	height;
	unsigned int			bars[8] = {0xEA80EA80, 0xD292D210, 0xA910A9A5, 0x90229035, 0x6ADD6ACA, 0x51EF515A, 0x286D28EF, 0x10801080};
	
	theFrame->GetBytes((void**)&nextWord);
	width = theFrame->GetWidth();
	height = theFrame->GetHeight();

	for (unsigned y = 0; y < height; y++)
	{
		for (unsigned x = 0; x < width; x+=2)
		{
			*(nextWord++) = bars[(x * 8) / width];
		}
	}
}

void	FillBlack (IDeckLinkVideoFrame* theFrame)
{
	unsigned int*			nextWord;
	unsigned long	width;
	unsigned long	height;
	unsigned long	wordsRemaining;
	
	theFrame->GetBytes((void**)&nextWord);
	width = theFrame->GetWidth();
	height = theFrame->GetHeight();
	
	wordsRemaining = (width*2 * height) / 4;
	
	while (wordsRemaining-- > 0)
		*(nextWord++) = 0x10801080;
}
